Skip to content

⚡ Spark: Dialog backdrop click and native event sync - #66

Merged
galiprandi merged 1 commit into
mainfrom
spark/dialog-enhancements-4682436667730776089
May 26, 2026
Merged

⚡ Spark: Dialog backdrop click and native event sync#66
galiprandi merged 1 commit into
mainfrom
spark/dialog-enhancements-4682436667730776089

Conversation

@galiprandi

@galiprandi galiprandi commented May 26, 2026

Copy link
Copy Markdown
Owner

💡 What: Enhanced the Dialog component with two key features:

  1. Support for closing the dialog when clicking on the backdrop (via closeOnBackdropClick prop).
  2. Synchronization between React state and the native HTML <dialog> element's internal state.

🎯 Why:

  • Closing on backdrop click is a common UX pattern for modals.
  • Native <dialog> elements can be closed via the ESC key or other browser-specific triggers. Without syncing with the native close event, the React state (and onClose callback) would remain out of sync.

📦 What it adds:

  • closeOnBackdropClick prop to DialogProps.
  • Automatic synchronization with native close events.

🚀 How to use:

<Dialog 
  behavior="modal" 
  closeOnBackdropClick={true} 
  onClose={() => console.log('Closed!')}
>
  <p>Click outside or press ESC to close</p>
</Dialog>

♻️ Deps Free: Verified, only uses React and native Browser APIs.

🎨 Harmony: Follows existing Dialog patterns and maintains backward compatibility.


PR created automatically by Jules for task 4682436667730776089 started by @galiprandi

Summary by CodeRabbit

  • New Features

    • Dialog component now supports optional closeOnBackdropClick prop to automatically close the dialog when users click outside its content area (disabled by default).
  • Documentation

    • Updated Dialog component documentation and changelog with new prop details and native event handling patterns.

Review Change Stack

- Added `closeOnBackdropClick` prop to `Dialog` component (default: `false`).
- Implemented state synchronization with native `close` event to handle ESC key and other native closing triggers.
- Updated documentation and unit tests to cover new functionality.
- Optimized `useEffect` by leveraging native event listeners for state management.
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The Dialog component gains a new closeOnBackdropClick prop that enables closing the dialog when clicking outside the content area. The implementation introduces native event handlers that synchronize the native <dialog> element's close event with React state, replacing imperative effect-based closing.

Changes

Dialog closeOnBackdropClick Feature

Layer / File(s) Summary
DialogProps contract and close-on-backdrop prop
lib/components/Dialog/index.tsx
The closeOnBackdropClick?: boolean prop is added to DialogProps interface and destructured in the component with a default value of false.
Native close event handler and backdrop click logic
lib/components/Dialog/index.tsx
The component introduces handleNativeClose to listen to native close events and update React state, and handleClick to detect clicks outside the dialog bounds. Both handlers are wired to the <dialog> element's onClose and onClick props; the effect dependency list is updated to remove the onClose prop dependency.
Test coverage for native close events and backdrop clicks
lib/components/Dialog/index.test.tsx
The mocked HTMLDialogElement.close now dispatches native close events. Tests verify onClose triggering from both prop-driven closing and manual close events. New tests use mocked bounding box geometry to verify backdrop-click closing behavior.
Changelog and README documentation
.axioma/spark.md, README.md
A changelog entry describes the React/native sync pattern, and the README prop table documents the new closeOnBackdropClick prop.

Sequence Diagram

sequenceDiagram
  participant User
  participant Dialog as Dialog Element
  participant ClickHandler as closeOnBackdropClick Handler
  participant CloseHandler as onClose Handler
  participant State as Dialog State

  User->>Dialog: click on backdrop
  Dialog->>ClickHandler: trigger onClick
  ClickHandler->>ClickHandler: check if click outside bounds
  ClickHandler->>Dialog: call close()
  Dialog->>Dialog: remove open attribute
  Dialog->>Dialog: dispatch close event
  Dialog->>CloseHandler: native close event
  CloseHandler->>State: set open = false
  CloseHandler->>CloseHandler: call onClose callback
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A dialog grows wise, now listening well,
To backdrop clicks and native bells,
React and DOM in sync do play,
Closing gracefully, the rabbit way! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding closeOnBackdropClick functionality and synchronizing React state with native dialog element events.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spark/dialog-enhancements-4682436667730776089

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/components/Dialog/index.tsx`:
- Around line 70-73: handleNativeClose currently closes the dialog and calls
onClose but is declared inline, risking a stale closure over the onClose prop;
update handleNativeClose to either (a) wrap it in useCallback with onClose and
setOpen in its dependency array so it always calls the latest onClose, or (b)
store the latest onClose in a ref (e.g., onCloseRef.current) and have
handleNativeClose call that ref so the handler need not be re-created — change
the implementation referencing the handleNativeClose function, the setOpen call,
and the onClose prop accordingly.

In `@README.md`:
- Line 258: Update the README table entry for the closeOnBackdropClick prop to
note that it only applies when behavior is set to 'modal'; reference the prop
name closeOnBackdropClick and the behavior prop (behavior="modal") so readers
know this is modal-only (matching the JSDoc on the Dialog interface and the
implementation check in Dialog component).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e6094e48-7835-478a-a2fa-b114d10ebb4a

📥 Commits

Reviewing files that changed from the base of the PR and between 8478c6e and 5c32b9e.

📒 Files selected for processing (4)
  • .axioma/spark.md
  • README.md
  • lib/components/Dialog/index.test.tsx
  • lib/components/Dialog/index.tsx

Comment on lines +70 to +73
const handleNativeClose = () => {
setOpen(false)
onClose?.()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Stale closure risk: handleNativeClose may capture an outdated onClose callback.

The handleNativeClose function is defined inline and will capture the onClose prop from the render where it was created. If the parent component passes a new onClose function on subsequent renders (e.g., due to inline arrow functions or changing dependencies), the event handler will continue invoking the stale callback until the component re-renders and re-attaches the handler.

This is a common pitfall in React event handlers that capture props or state.

🔄 Proposed fix: wrap in useCallback or use a ref

Option 1: Wrap in useCallback (simpler)

+const handleNativeClose = useCallback(() => {
+    setOpen(false)
+    onClose?.()
+}, [onClose])
-const handleNativeClose = () => {
-    setOpen(false)
-    onClose?.()
-}

Option 2: Use a ref to always call the latest callback (no re-render on prop change)

+const onCloseRef = useRef(onClose)
+useEffect(() => {
+    onCloseRef.current = onClose
+}, [onClose])
+
+const handleNativeClose = useCallback(() => {
+    setOpen(false)
+    onCloseRef.current?.()
+}, [])
-const handleNativeClose = () => {
-    setOpen(false)
-    onClose?.()
-}

Option 2 is preferred if you want to avoid re-attaching the event listener when onClose changes, though for dialog close events this overhead is negligible.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleNativeClose = () => {
setOpen(false)
onClose?.()
}
const handleNativeClose = useCallback(() => {
setOpen(false)
onClose?.()
}, [onClose])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/components/Dialog/index.tsx` around lines 70 - 73, handleNativeClose
currently closes the dialog and calls onClose but is declared inline, risking a
stale closure over the onClose prop; update handleNativeClose to either (a) wrap
it in useCallback with onClose and setOpen in its dependency array so it always
calls the latest onClose, or (b) store the latest onClose in a ref (e.g.,
onCloseRef.current) and have handleNativeClose call that ref so the handler need
not be re-created — change the implementation referencing the handleNativeClose
function, the setOpen call, and the onClose prop accordingly.

Repository owner deleted a comment from coderabbitai Bot May 26, 2026
@galiprandi
galiprandi merged commit 449fd69 into main May 26, 2026
5 checks passed
@galiprandi
galiprandi deleted the spark/dialog-enhancements-4682436667730776089 branch June 15, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant